<!DOCTYPE html>
<html lang="en">

	<head>
		<meta charset="UTF-8">
		<title>Title</title>
		<style>
			html body .box {
				width: 100%;
				height: 800px;
			}

			.processContainer {
				width: 100%;
				height: 100%;
				border: 1px dashed black;
				border-radius: 6px;
			}

			.processNode {
				width: 100px;
				height: 60px;
				position: absolute;
				background-color: #eee;
				border: 1px solid black;
				border-radius: 4px;
				text-align: center;
				line-height: 60px;
			}

			.processNodeCircle {
				width: 100px;
				height: 60px;
				position: absolute;
				background-color: #eee;
				border: 1px solid black;
				border-radius: 4px;
				text-align: center;
				line-height: 60px;
				border-radius: 100px;
			}

			.line {
				position: absolute;
				background: black;
				height: 1px;
				z-index: 1;
			}
		</style>
	</head>

	<body>
		<div class="box">
			<!-- 流程容器 -->
			<div id="processContainer" class="processContainer"></div>
		</div>
		<script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
		<script>
			$(function() {

				/**
				 * 渲染流程
				 * 
				 * @param el 画布
				 * @param file 流程文件
				 * */
				function render(el, file) {
					$.getJSON(file, function(data) {
						let nodes = data.nodes;
						
						// 解决问题的核心方法
						auto(nodes);
						
						// 以下代码，主要做流程解析
						let nodeElements = createNodes(nodes);
						let lineElements = createLines(nodes);
						$.each(nodeElements, (index, element) => {
							$(el).append(element)
						})
						$.each(lineElements, (index, element) => {
							$(el).append(element)
						})
					})
				}

				/**
				 * 创建节点
				 * 
				 * @param nodes 节点集合
				 * */
				function createNodes(nodes) {
					let arr = [];
					$.each(nodes, function(i, node) {
						arr.push(createNode(node));
					})
					return arr;
				}

				/**
				 * 创建节点
				 * 
				 * @param node 节点定义
				 * */
				function createNode(node) {
					let element = document.createElement("div");
					element.style.left = node.metadata.x;
					element.style.top = node.metadata.y;
					element.textContent = node.name;
					if (node.shape === "0") {
						element.style.width = node.metadata.s;
						element.style.height = node.metadata.s;
						element.style.lineHeight = node.metadata.s;
						element.className = "processNodeCircle";
					} else {
						element.style.width = node.metadata.w;
						element.style.height = node.metadata.h;
						element.style.lineHeight = node.metadata.h;
						element.className = "processNode";
					}
					return element;
				}
				
				/**
				 * 点点连线
				 * 
				 * @param nodes 节点集合
				 * */
				function createLines(nodes) {
					let arr = [];
					$.each(nodes, function(i, node) {
						if(node.form!=null){
							$.each(nodes, function(i, formNode) {	
								if(node.form instanceof Array) {
									$.each(node.form, (i, formName) => {
										if(formName === formNode.id) {
											arr.push(createLine(formNode, node))
										}
									})
								} else {
									if(node.form === formNode.id){
										arr.push(createLine(formNode, node))
									}
								}
							})
						}
					})
					return arr;
				}

				/**
				 * 点点连线
				 * 
				 * @param start 开始点
				 * @param end 结束点
				 * */
				function createLine(start, end) {
					//起点元素中心坐标
					var y_start;
					var x_start;
					if(start.shape === "0") {
						y_start = Number(start.metadata.y.replace("px", "")) + Number(start.metadata.s.replace("px", "")) / 2;
						x_start = Number(start.metadata.x.replace("px", "")) + Number(start.metadata.s.replace("px", "")) / 2;
					} else {
						y_start = Number(start.metadata.y.replace("px", "")) + Number(start.metadata.h.replace("px", "")) / 2;
						x_start = Number(start.metadata.x.replace("px", "")) + Number(start.metadata.w.replace("px", "")) / 2;
					} 
					//终点元素中心坐标
					var y_end;
					var x_end;
					if(end.shape === "0") {
						//终点元素中心坐标
						y_end = Number(end.metadata.y.replace("px", "")) + Number(end.metadata.s.replace("px", "")) / 2;
						x_end = Number(end.metadata.x.replace("px", "")) + Number(end.metadata.s.replace("px", "")) / 2;
					} else {
						//终点元素中心坐标
						y_end = Number(end.metadata.y.replace("px", "")) + Number(end.metadata.h.replace("px", "")) / 2;
						x_end = Number(end.metadata.x.replace("px", "")) + Number(end.metadata.w.replace("px", "")) / 2;
					}
					//用勾股定律计算出斜边长度及其夹角（即连线的旋转角度）
					var lx = x_end - x_start;
					var ly = y_end - y_start;
					var length = Math.sqrt(lx * lx + ly * ly);
					var c = 360 * Math.atan2(ly, lx) / (2 * Math.PI);
					//连线中心坐标
					var midX = (x_end + x_start) / 2;
					var midY = (y_end + y_start) / 2
					var deg = c <= -90 ? (360 + c) : c; //负角转换为正角
					return `<div class='line' style='top:${midY}px;left:${midX-length/2}px;width:${length}px;transform:rotate(${deg}deg);'></div>`
				}

				/**
				 * 计算位置
				 * <p>
				 * 预设所有节点没有坐标的前提下，我们需要给出合理的坐标位置
				 * 
				 * 有向无环的流程，在节点设计时，我指定了所有节点 form (上一个节点)，当某个节点不存在 form 时，说明时起始节点，我们需要先找到他
				 * 
				 * @param nodes 节点集合
				 * */
				function auto(nodes) {
					let startNode = [];
					var x = 0;
					var y = 0;
					$.each(nodes, function(i, node) {
						if(node.form === undefined ) {
							// 如果你是开始节点，我给你初始位置
							node.metadata.x = x + 200 + "px";
							node.metadata.y = y + 200 + "px";
							startNode.push(node);
						}
					})
					// 当所有的初始节点完成后，通过递归完成后续节点的位置计算
					auto1(nodes, startNode);
				}
				
				function auto1(nodes, parentNodes) {
					let flag = true;
					let startNode = [];
					let isFirst = 0;
					let x = Number(parentNodes[0].metadata.x.replace("px","")) + 200
					let y = Number(parentNodes[0].metadata.y.replace("px",""))
					
					$.each(parentNodes, function(i, parentNode){
						$.each(nodes, function(i, node) {
							if(node.form instanceof Array) {
								if(node.form[0] === parentNode.id ) {
									if(isFirst > 0 ){
										y = y + 200;
									} else {
										isFirst ++;
									}
									node.metadata.x = x + "px";
									node.metadata.y = y + "px";
									startNode.push(node);
									flag = false;
								}
							} else {
								if(node.form === parentNode.id ) {
									if(isFirst > 0 ){
										y = y + 200;
									} else {
										isFirst ++;
									}
									node.metadata.x = x + "px";
									node.metadata.y = y + "px";
									startNode.push(node);
									flag = false;
								}
							}
						})
					})
					if(flag) {
						return;
					}
					auto1(nodes,startNode);
				}

				render("#processContainer", "./index.json")
			})
		</script>
	</body>

</html>
